/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

// this is all custom draw and key funcs
// to do - use the enumerated save list instead...
#include "quakedef.h"
#include "menu.shared.h"

// to do - make a scrollable menu here
#define	MAX_SAVEGAMES	19

struct savegame_t
{
	char filename[SAVEGAME_COMMENT_LENGTH + 1];
	char mapname[64];
	int skill;
	int current_kills;
	int total_kills;
	int current_secrets;
	int total_secrets;
	float time;
	BOOL loadable;

	void DrawInfo (int y)
	{
		M_DrawLabelValue (204, 276, y,      "    Map:", this->mapname);
		M_DrawLabelValue (204, 276, y + 10, "  Skill:", M_GetSkill (this->skill));
		M_DrawLabelValue (204, 276, y + 20, "   Time:", Q_TimeToStringUnpadded ((int) (this->time + 0.5f)));

		if (this->total_kills)
			M_DrawLabelValue (204, 276, y + 30,  "  Kills:", va ("%i/%i", this->current_kills, this->total_kills));
		else M_DrawLabelValue (204, 276, y + 30, "  Kills:", va ("%i", this->current_kills));

		if (this->total_secrets)
			M_DrawLabelValue (204, 276, y + 40,  "Secrets:", va ("%i/%i", this->current_secrets, this->total_secrets));
		else M_DrawLabelValue (204, 276, y + 40, "Secrets:", va ("%i", this->current_secrets));
	}

	void ScanSave (FILE *f)
	{
		char	*data = NULL;
		int		version;
		float	junk;

		QSCRATCHLOCKER Lock ((void **) &data, __FUNCTION__);
		fscanf (f, "%i\n", &version);

		if (version == SAVEGAME_VERSION)
		{
			fscanf (f, "%s\n", data);
			strncpy (this->filename, data, sizeof (this->filename) - 1);

			// change _ back to space
			for (int j = 0; j < SAVEGAME_COMMENT_LENGTH; j++)
				if (this->filename[j] == '_')
					this->filename[j] = ' ';

			// rip out the "kills" portion
			for (int j = SAVEGAME_COMMENT_LENGTH - 4; j; j--)
			{
				if (!strnicmp (&this->filename[j], "kills", 5))
				{
					this->filename[j] = 0;
					break;
				}
			}

			// absolute-bound the comment at the max of our available space
			this->filename[23] = 0;

			// look for other info - skip over the spawn parms
			for (int i = 0; i < NUM_SPAWN_PARMS; i++)
				fscanf (f, "%f\n", &junk);

			// this silliness is so we can load 1.06 save files, which have float skill values
			fscanf (f, "%f\n", &junk);
			this->skill = (int) (junk + 0.1);

			fscanf (f, "%s\n", this->mapname);
			fscanf (f, "%f\n", &this->time);

			// skip over the lightstyles
			for (int i = 0; i < MAX_LIGHTSTYLES; i++)
				fscanf (f, "%s\n", data);

			// now we're into edicts and the first edict is the player so parse kills and secrets out of it
			// fread is allowed to early-out if it hits eof so just read enough to be sure of getting the first edict
			fread (data, 65536, 1, f);

			// parse out the opening brace
			if ((data = COM_Parse (data)) != NULL)
			{
				if (com_token[0])
				{
					if (!strcmp (com_token, "{"))
					{
						// parse out the globals (which will be the first edict)
						// unlike doing it for real, we just break on error rather than sys_error
						for (;;)
						{
							char keyname[64];

							// parse key
							if ((data = COM_Parse (data)) == NULL) break;
							if (com_token[0] == '}') break;
							if (!data) break;

							Q_strncpy (keyname, com_token, 63);

							// parse value
							if (!(data = COM_Parse (data))) break;
							if (com_token[0] == '}') break;

							// interpret - these are stored as floats in the save file
							if (!_stricmp (keyname, "total_secrets")) this->total_secrets = (int) (atof (com_token) + 0.1f);
							if (!_stricmp (keyname, "found_secrets")) this->current_secrets = (int) (atof (com_token) + 0.1f);
							if (!_stricmp (keyname, "total_monsters")) this->total_kills = (int) (atof (com_token) + 0.1f);
							if (!_stricmp (keyname, "killed_monsters")) this->current_kills = (int) (atof (com_token) + 0.1f);
						}
					}
				}
			}

			// and now it's loadable
			this->loadable = TRUE;
		}
	}
};


savegame_t savegames[MAX_SAVEGAMES];
BOOL m_savelistdirty = TRUE;

// using a shared cursor for both menus
static int SaveLoadCursor = 0;


void M_DirtySaveList (void)
{
	// also invalidating the mapshot because it will probably be different
	m_savelistdirty = TRUE;
	Draw_InvalidateMapshot ();
}


class qmenu_saveload_t : public qmenu_t
{
public:
	virtual void EnterFunc (void) = 0;

	BOOL KeyFunc (int key)
	{
		switch (key)
		{
		case K_DEL:
			if (savegames[SaveLoadCursor].loadable)
			{
				if (SCR_ModalMessage ("Are you sure you want to\ndelete this save?\n"))
				{
					// delete the file
					DeleteFile (va ("%s/save/s%i.sav", com_gamedir, SaveLoadCursor));
					DeleteFile (va ("%s/save/s%i.tga", com_gamedir, SaveLoadCursor));
					Con_Printf (PRINT_DEFAULT, "Deleted %s/save/s%i.sav\n", com_gamedir, SaveLoadCursor);

					// flag it as unloadable for this menu and dirty the list so it will rescan fully on next entry
					// this can be faster if we're deleting a lot of save games in one menu session as it won't need
					// to fully rescan every time
					savegames[SaveLoadCursor].loadable = FALSE;
					M_DirtySaveList ();
				}
			}

			break;

		case K_ESCAPE: this->LeaveMenu (); break;
		case K_ENTER: this->SaveLoadAction (); break;
		case K_UPARROW: this->Navigate (-1); break;
		case K_LEFTARROW: this->Navigate (-1); break;
		case K_DOWNARROW: this->Navigate (1); break;
		case K_RIGHTARROW: this->Navigate (1); break;
		}

		if (SaveLoadCursor < 0) SaveLoadCursor = MAX_SAVEGAMES - 1;
		if (SaveLoadCursor >= MAX_SAVEGAMES) SaveLoadCursor = 0;

		return TRUE;
	}

protected:
	void Navigate (int dir)
	{
		m_soundlevel = m_sound_navigate;
		SaveLoadCursor += dir;

		if (SaveLoadCursor < 0) SaveLoadCursor = MAX_SAVEGAMES - 1;
		if (SaveLoadCursor >= MAX_SAVEGAMES) SaveLoadCursor = 0;
	}

	void ScanSaves (void)
	{
		if (m_savelistdirty)
		{
			// "/save" was created in FS_InitFilesystem
			for (int i = 0; i < MAX_SAVEGAMES; i++)
			{
				// it's not loadable until we confirm that it is
				savegames[i].loadable = FALSE;

				char name[MAX_OSPATH];
				FILE *f;

				sprintf (name, "%s/save/s%i.sav", com_gamedir, i);

				if ((f = fopen (name, "r")) != NULL)
				{
					savegames[i].ScanSave (f);
					fclose (f);
				}
			}

			m_savelistdirty = FALSE;
		}
	}

	void CustomDraw (void)
	{
		M_DrawTextBox (-16, 34, 24, 24);
		M_DrawHighlight (-8, 44 + SaveLoadCursor * 10, 192);

		for (int i = 0; i < MAX_SAVEGAMES; i++)
		{
			if (savegames[i].loadable)
				M_PrintWhite (-4, 44 + 10 * i, savegames[i].filename);
			else M_Print (-4, 44 + 10 * i, "unused");
		}

		if (savegames[SaveLoadCursor].loadable)
		{
			// draw our mapshot
			M_DrawMapshot (200, 42, va ("save/s%i", SaveLoadCursor));

			// draw our savegame info
			savegames[SaveLoadCursor].DrawInfo (179);

			// draw the delete hint
			M_DrawCommand (40 + 10 * MAX_SAVEGAMES + 16, "<DEL> : Delete this save");
		}
		else M_DrawMapshot (200, 42, "*** unused ***");
	}

	virtual void SaveLoadAction (void) = 0;
};


class qmenu_save_t : public qmenu_saveload_t
{
public:
	qmenu_save_t (void)
	{
		// add our menu options
		this->AddOption (new qmenu_banner_t ("gfx/p_save.lmp"));
		this->AddOption (new qmenu_divider_t ());

		// and register our menu
		this->Register (m_save);
	}

	void EnterFunc (void)
	{
		if (!sv.active) return;
		if (cl.intermission) return;
		if (svs.maxclients != 1) return;

		this->EnterMenu ();
		this->ScanSaves ();
	}

protected:
	void SaveLoadAction (void)
	{
		m_state = m_none;
		Key_SetDest (key_game);
		Cbuf_AddText (va ("save s%i\n", SaveLoadCursor));
	}
} menu_save;


class qmenu_load_t : public qmenu_saveload_t
{
public:
	qmenu_load_t (void)
	{
		// add our menu options
		this->AddOption (new qmenu_banner_t ("gfx/p_load.lmp"));
		this->AddOption (new qmenu_divider_t ());

		// and register our menu
		this->Register (m_load);
	}

	void EnterFunc (void)
	{
		this->EnterMenu ();
		this->ScanSaves ();
	}

protected:
	void SaveLoadAction (void)
	{
		if (!savegames[SaveLoadCursor].loadable)
			return;

		m_state = m_none;
		Key_SetDest (key_game);

		// Host_Loadgame_f can't bring up the loading plaque because too much
		// stack space has been used, so do it now
		SCR_BeginLoadingPlaque ();

		// issue the load command
		Cbuf_AddText (va ("load s%i\n", SaveLoadCursor));
	}
} menu_load;


